using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;
using Pinwheel.Toolkits.DigestGenerator;
using Unity.Collections.LowLevel.Unsafe;

using Guid = Pinwheel.Toolkits.DigestGenerator.Guid;

namespace Pinwheel.Toolkits.Reflection
{
    public unsafe struct FieldPtr
    {
        public bool IsValueType;
        public TypeCode TypeCode;
        public int FieldOffset;
        public Guid Guid;
        
        public bool SetValue<T>(object obj, T value, Type fieldType = null) where T : unmanaged
        {
            return SetValue<T>(FieldPtrUtil.GetPointer(obj), value, fieldType);
        }

        public bool SetValue<T>(IntPtr ptrObj, T value, Type fieldType = null) where T : unmanaged
        {
            if (IntPtr.Zero == ptrObj) return false;
            IntPtr ptrField = ptrObj + FieldOffset;
            try
            {
                *(T*) ptrField = value;
            }
            catch
            {
                throw new Exception($"SetValue failed, FieldPtr TypeCode->{TypeCode} value TypeCode->{Type.GetTypeCode(typeof(T))} fieldType->{fieldType}");
            }
            return true;
        }
        
        public T GetValue<T>(object obj, Type fieldType = null) where T : unmanaged
        {
            return GetValue<T>(FieldPtrUtil.GetPointer(obj), fieldType);
        }
        
        public T GetValue<T>(IntPtr ptrObj, Type fieldType = null) where T : unmanaged
        {
            if (IntPtr.Zero == ptrObj) return default;
            IntPtr ptrField = ptrObj + FieldOffset;
            try
            {
                return *(T*) ptrField;
            }
            catch
            {
                throw new Exception($"GetValue failed, FieldPtr TypeCode->{TypeCode} value TypeCode->{Type.GetTypeCode(typeof(T))} fieldType->{fieldType}");
            }
        }
        
    }
    
    public class FieldPtrRef
    {
        public string FieldName;
        public Type FieldType;
        public FieldPtr FieldPtr;
        public Guid Guid;
        
        public bool SetValue<T>(object obj, T value, Type fieldType = null) where T : unmanaged
        {
            return FieldPtr.SetValue<T>(obj, value, fieldType);
        }
        
        public T GetValue<T>(object obj, Type fieldType = null) where T : unmanaged
        {
            return FieldPtr.GetValue<T>(obj, fieldType);
        }
    }

    public static class FieldPtrUtil
    {
        /// <summary> 缓存 </summary>
        private static Dictionary<Type, Dictionary<string, FieldPtrRef>> nameCache =
            new Dictionary<Type, Dictionary<string, FieldPtrRef>>();
        
        private static Dictionary<Guid, FieldPtrRef> refCache = new Dictionary<Guid, FieldPtrRef>();
        private static Dictionary<Guid, FieldPtr> ptrCache = new Dictionary<Guid, FieldPtr>();
        
        private static bool is64Bit = Environment.Is64BitProcess;

        public static FieldPtrRef GetFieldPtr(Type type, string fieldName)
        {
            // 查找一级缓存
            Dictionary<string, FieldPtrRef> wrapperDic = null;
            if (!nameCache.TryGetValue(type, out wrapperDic))
            {
                wrapperDic = new Dictionary<string, FieldPtrRef>();
                nameCache.Add(type, wrapperDic);
            }

            // 查找二级缓存
            FieldPtrRef ptrRef = null;
            if (!wrapperDic.TryGetValue(fieldName, out ptrRef))
            {
                FieldInfo field = type.GetField(fieldName, BindingFlags.Public | BindingFlags.Instance);
                if (field == null) throw new Exception(type.Name + " field is null:" + fieldName);
                ptrRef = new FieldPtrRef();
                ptrRef.FieldName = fieldName;
                ptrRef.FieldType = field.FieldType;
                
                Guid guid = GuidGenerator.Guid();
                FieldPtr fieldPtr = new FieldPtr();
                fieldPtr.Guid = guid;
                fieldPtr.IsValueType = field.FieldType.IsValueType;
                fieldPtr.TypeCode = Type.GetTypeCode(field.FieldType);
                fieldPtr.FieldOffset = GetFieldOffset(field);

                ptrRef.FieldPtr = fieldPtr;
                ptrRef.Guid = guid;
                wrapperDic.Add(fieldName, ptrRef);
                refCache.Add(guid, ptrRef);
                ptrCache.Add(guid, fieldPtr);
            }

            return ptrRef;
        }
        
        /// <summary> 获取对象地址 </summary>
        public static unsafe IntPtr GetPointer(object obj)
        {
            ulong gcHandle;
            IntPtr ptr = (IntPtr)UnsafeUtility.PinGCObjectAndGetAddress(obj, out gcHandle);
            UnsafeUtility.ReleaseGCObject(gcHandle);
            return ptr;
        }
        
        /// <summary> 获取字段地址偏移量 </summary>
        public static unsafe int GetFieldOffset(FieldInfo field)
        {
            return *(short*)(field.FieldHandle.Value + 24);
        }


        /// <summary> 清理缓存数据 </summary>
        public static void ClearCache()
        {
            nameCache.Clear();
            refCache.Clear();
            ptrCache.Clear();
        }
    }
}